home *** CD-ROM | disk | FTP | other *** search
- /* GNU DIFF main routine.
- Copyright (C) 1988, 1989 Free Software Foundation, Inc.
-
- This file is part of GNU DIFF.
-
- GNU DIFF is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 1, or (at your option)
- any later version.
-
- GNU DIFF is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with GNU DIFF; see the file COPYING. If not, write to
- the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. */
-
- /* GNU DIFF was written by Mike Haertel, David Hayes,
- Richard Stallman and Len Tower. */
-
- #define GDIFF_MAIN
- #include "regex.h"
- #include "diff.h"
- #include "getopt.h"
-
- static char *option_list (char **, int);
- static int get_filestat (char *, struct file_data *);
- static int open (char *);
- static void close (int);
-
- /* Nonzero for -r: if comparing two directories,
- compare their common subdirectories recursively. */
-
- int recursive;
-
- /* For debugging: don't do discard_confusing_lines. */
-
- int no_discards;
-
- /* Return a string containing the command options with which diff was invoked.
- Spaces appear between what were separate ARGV-elements.
- There is a space at the beginning but none at the end.
- If there were no options, the result is an empty string.
-
- Arguments: OPTIONVEC, a vector containing separate ARGV-elements, and COUNT,
- the length of that vector. */
-
- static char *option_list (char **optionvec, int count)
- {
- int i;
- int length = 0;
- char *result;
-
- for (i = 0; i < count; i++)
- length += strlen (optionvec[i]) + 1;
-
- result = (char *) xmalloc (length + 1);
- result[0] = 0;
-
- for (i = 0; i < count; i++)
- {
- strcat (result, " ");
- strcat (result, optionvec[i]);
- }
-
- return result;
- }
-
- /* Argument processing */
-
- #define OPTIONS "?0123456789abBcC:dD:efF:hHiI:lL:nNpqrsS:tTuvw"
-
- /* The numbers 129 and 130 that appear in the fourth element
- for the context and unidiff entries are used as a way of
- telling the big switch in `main' how to process those options. */
-
- static struct option longopts[] =
- {
- {"ignore-blank-lines", 0, 0, 'B'},
- {"context", 2, 0, 129},
- {"ifdef", 1, 0, 'D'},
- {"show-function-line", 1, 0, 'F'},
- {"speed-large-files", 0, 0, 'H'},
- {"ignore-matching-lines", 1, 0, 'I'},
- {"file-label", 1, 0, 'L'},
- {"entire-new-files", 0, 0, 'N'},
- {"new-files", 0, 0, 'N'},
- {"starting-file", 1, 0, 'S'},
- {"initial-tab", 0, 0, 'T'},
- {"text", 0, 0, 'a'},
- {"all-text", 0, 0, 'a'},
- {"ascii", 0, 0, 'a'},
- {"ignore-space-change", 0, 0, 'b'},
- {"minimal", 0, 0, 'd'},
- {"ed", 0, 0, 'e'},
- {"reversed-ed", 0, 0, 'f'},
- {"ignore-case", 0, 0, 'i'},
- {"print", 0, 0, 'l'},
- {"rcs", 0, 0, 'n'},
- {"show-c-function", 0, 0, 'p'},
- {"binary", 0, 0, 'q'},
- {"brief", 0, 0, 'q'},
- {"recursive", 0, 0, 'r'},
- {"report-identical-files", 0, 0, 's'},
- {"expand-tabs", 0, 0, 't'},
- {"ignore-all-space", 0, 0, 'w'},
- {"unified", 2, 0, 130},
- {"version", 0, 0, 'v'},
- {0, 0, 0, 0}
- };
-
- #define USAGE \
- "Usage: Diff [options] file1 file2\n" \
- "\n" \
- "Options:\n" \
- "Short Long Description\n" \
- "-99 Set amount of context to 99\n" \
- "-a +text +all-text +ascii Treat all files as text\n" \
- "-b +ignore-space-character Ignore changes in spaces (compare -w)\n" \
- "-B +ignore-blank-lines Ignore blank lines\n" \
- "-C +context[=lines] Context diff, with <lines=3> context\n" \
- "-c Context diff, with default context\n" \
- "-d +minimal Produce a guaranteed minimal diff\n" \
- "-D +ifdef=id Mark changes with <#ifdef id>\n" \
- "-e +ed Generate an ed script\n" \
- "-f +reversed-ed Generate a reversed ed script\n" \
- "-F +show-function-line=regex Context header matches <regex>\n" \
- "-h Split files [not implemented]\n" \
- "-H +speed-large-files Run faster on large files\n" \
- "-i +ignore-case Ignore case when comparing\n" \
- "-I +ignore-matching-lines=regex Ignore lines which match <regex>\n" \
- "-l +print Send output to \"pr\"\n" \
- "-L +file-label=label Label file with <label>\n" \
- "-n +rcs Generate RCS output\n" \
- "-N +new-files +entire-new-files Treat missing files as empty\n" \
- "-p +show-c-function Context header is last C function\n" \
- "-q +binary Treat all files as binary\n" \
- "-r +recursive Recursively check sub-directories\n" \
- "-s +report-identical-files Report files which match\n" \
- "-S +starting-file=file Start directory scan at <file>\n" \
- "-t +expand-tabs Expand tabs in output\n" \
- "-T +initial-tab Precede output lines with TAB\n" \
- "-v +version Print the version of diff\n" \
- "-u Unified diff, with default context\n" \
- " +unified[=lines] Unified diff, with <lines=3> context\n" \
- "-w +ignore-all-space Ignore all spaces (compare -b)\n"
-
- /* Main program */
-
- int main (int argc, char *argv[])
- {
- int val;
- int c;
- int prev = -1;
- int longind;
- extern char *version_string;
-
- program = argv[0];
-
- /* Do our initializations. */
- output_style = OUTPUT_NORMAL;
- always_text_flag = FALSE;
- ignore_space_change_flag = FALSE;
- ignore_all_space_flag = FALSE;
- length_varies = FALSE;
- ignore_case_flag = FALSE;
- ignore_blank_lines_flag = FALSE;
- ignore_regexp = 0;
- function_regexp = 0;
- print_file_same_flag = FALSE;
- entire_new_file_flag = FALSE;
- no_details_flag = FALSE;
- context = -1;
- line_end_char = '\n';
- tab_align_flag = FALSE;
- tab_expand_flag = FALSE;
- recursive = FALSE;
- paginate_flag = FALSE;
- ifdef_string = NULL;
- heuristic = FALSE;
- dir_start_file = NULL;
- msg_chain = NULL;
- msg_chain_end = NULL;
- no_discards = 0;
-
- /* Decode the options. */
-
- while ((c = getopt_long (argc, argv, OPTIONS, longopts, &longind)) != OPT_END)
- {
- if (c == OPT_LONG) /* Long option. */
- c = longopts[longind].val;
- switch (c)
- {
- /* All digits combine in decimal to specify the context-size. */
- case '1':
- case '2':
- case '3':
- case '4':
- case '5':
- case '6':
- case '7':
- case '8':
- case '9':
- case '0':
- if (context == -1)
- context = 0;
- /* If a context length has already been specified,
- more digits allowed only if they follow right after the others.
- Reject two separate runs of digits, or digits after -C. */
- else if (prev < '0' || prev > '9')
- fatal ("context length specified twice");
-
- context = context * 10 + c - '0';
- break;
-
- case 'a':
- /* Treat all files as text files; never treat as binary. */
- always_text_flag = 1;
- break;
-
- case 'b':
- /* Ignore changes in amount of whitespace. */
- ignore_space_change_flag = 1;
- length_varies = 1;
- break;
-
- case 'B':
- /* Ignore changes affecting only blank lines. */
- ignore_blank_lines_flag = 1;
- break;
-
- case 'C':
- case 129: /* +context[=lines] */
- case 130: /* +unified[=lines] */
- if (optarg)
- {
- if (context >= 0)
- fatal ("context length specified twice");
- {
- char *p;
- for (p = optarg; *p; p++)
- if (*p < '0' || *p > '9')
- fatal ("invalid context length argument");
- }
- context = atoi (optarg);
- }
-
- /* Falls through. */
- case 'c':
- /* Make context-style output. */
- specify_style (c == 130 ? OUTPUT_UNIFIED : OUTPUT_CONTEXT);
- break;
-
- case 'd':
- /* Don't discard lines. This makes things slower (sometimes much
- slower) but will find a guaranteed minimal set of changes. */
- no_discards = 1;
- break;
-
- case 'D':
- /* Make merged #ifdef output. */
- specify_style (OUTPUT_IFDEF);
- ifdef_string = optarg;
- break;
-
- case 'e':
- /* Make output that is a valid `ed' script. */
- specify_style (OUTPUT_ED);
- break;
-
- case 'f':
- /* Make output that looks vaguely like an `ed' script
- but has changes in the order they appear in the file. */
- specify_style (OUTPUT_FORWARD_ED);
- break;
-
- case 'F':
- /* Show, for each set of changes, the previous line that
- matches the specified regexp. Currently affects only
- context-style output. */
- function_regexp = optarg;
- break;
-
- case 'h':
- /* Split the files into chunks of around 1500 lines
- for faster processing. Usually does not change the result.
-
- This currently has no effect. */
- break;
-
- case 'H':
- /* Turn on heuristics that speed processing of large files
- with a small density of changes. */
- heuristic = 1;
- break;
-
- case 'i':
- /* Ignore changes in case. */
- ignore_case_flag = 1;
- break;
-
- case 'I':
- /* Ignore changes affecting only lines that match the
- specified regexp. */
- ignore_regexp = optarg;
- break;
-
- case 'l':
- /* Pass the output through `pr' to paginate it. */
- paginate_flag = 1;
- break;
-
- case 'L':
- /* Specify file labels for `-c' output headers. */
- if (!file_label[0])
- file_label[0] = optarg;
- else if (!file_label[1])
- file_label[1] = optarg;
- else
- fatal ("too many file label options");
- break;
-
- case 'n':
- /* Output RCS-style diffs, like `-f' except that each command
- specifies the number of lines affected. */
- specify_style (OUTPUT_RCS);
- break;
-
- case 'N':
- /* When comparing directories, if a file appears only in one
- directory, treat it as present but empty in the other. */
- entire_new_file_flag = 1;
- break;
-
- case 'p':
- /* Make context-style output and show name of last C function. */
- specify_style (OUTPUT_CONTEXT);
- function_regexp = "^[_a-zA-Z].*\\(";
- break;
-
- case 'q':
- no_details_flag = 1;
- break;
-
- case 'r':
- /* When comparing directories,
- recursively compare any subdirectories found. */
- recursive = 1;
- break;
-
- case 's':
- /* Print a message if the files are the same. */
- print_file_same_flag = 1;
- break;
-
- case 'S':
- /* When comparing directories, start with the specified
- file name. This is used for resuming an aborted comparison. */
- dir_start_file = optarg;
- break;
-
- case 't':
- /* Expand tabs to spaces in the output so that it preserves
- the alignment of the input files. */
- tab_expand_flag = 1;
- break;
-
- case 'T':
- /* Use a tab in the output, rather than a space, before the
- text of an input line, so as to keep the proper alignment
- in the input line without changing the characters in it. */
- tab_align_flag = 1;
- break;
-
- case 'u':
- /* Output the context diff in unidiff format. */
- specify_style (OUTPUT_UNIFIED);
- break;
-
- case 'v':
- fprintf (stderr, "GNU diff version %s\n", version_string);
- exit (0);
-
- case 'w':
- /* Ignore horizontal whitespace when comparing lines. */
- ignore_all_space_flag = 1;
- length_varies = 1;
- break;
-
- case '?':
- fprintf(stderr, "%s", USAGE);
- exit (0);
-
- case OPT_ERR:
- default:
- exit (2);
- }
- prev = c;
- }
-
- if (optind != argc - 2)
- {
- fprintf(stderr, "Diff: Requires 2 arguments\n");
- exit (2);
- }
-
- if (ignore_regexp)
- {
- char *val;
- bzero (&ignore_regexp_compiled, sizeof ignore_regexp_compiled);
- val = re_compile_pattern (ignore_regexp, strlen (ignore_regexp),
- &ignore_regexp_compiled);
- if (val != 0)
- error ("%s: %s", ignore_regexp, val);
- ignore_regexp_compiled.fastmap = (char *) xmalloc (256);
- }
-
- if (function_regexp)
- {
- char *val;
- bzero (&function_regexp_compiled, sizeof function_regexp_compiled);
- val = re_compile_pattern (function_regexp, strlen (function_regexp),
- &function_regexp_compiled);
- if (val != 0)
- error ("%s: %s", function_regexp, val);
- function_regexp_compiled.fastmap = (char *) xmalloc (256);
- }
-
- if (output_style != OUTPUT_CONTEXT && output_style != OUTPUT_UNIFIED)
- context = 0;
- else if (context == -1)
- /* Default amount of context for -c. */
- context = 3;
-
- switch_string = option_list (argv + 1, optind - 1);
-
- val = compare_files (0, argv[optind], 0, argv[optind + 1], 0);
-
- /* Print any messages that were saved up for last. */
- print_message_queue ();
-
- if (ferror (stdout) || fclose (stdout) != 0)
- fatal ("write error");
- exit (val);
- }
-
- void specify_style (enum output_style style)
- {
- if (output_style != OUTPUT_NORMAL
- && output_style != style)
- error ("conflicting specifications of output style");
- output_style = style;
- }
-
- /* Compare two files (or dirs) with specified names
- DIR0/NAME0 and DIR1/NAME1, at level DEPTH in directory recursion.
- (if DIR0 is 0, then the name is just NAME0, etc.)
- This is self-contained; it opens the files and closes them.
-
- Value is 0 if files are identical, 1 if different,
- 2 if there is a problem opening them. */
-
- int compare_files (char *dir0, char *name0, char *dir1, char *name1, int depth)
- {
- static char Standard_Input[] = "Standard Input";
- struct file_data inf[2];
- register int i;
- int val;
- int errorcount = 0;
-
-
- /* If this is directory comparison, perhaps we have a file
- that exists only in one of the directories.
- If so, just print a message to that effect. */
-
- if (! entire_new_file_flag && (name0 == 0 || name1 == 0))
- {
- char *name = name0 == 0 ? name1 : name0;
- char *dir = name0 == 0 ? dir1 : dir0;
- message ("Only in %s: %s\n", dir, name);
- /* Return 1 so that diff_dirs will return 1 ("some files differ"). */
- return 1;
- }
-
- /* Mark any nonexistent file with -1 in the desc field. */
-
- inf[0].desc = name0 == 0 ? -1 : 0;
- inf[1].desc = name1 == 0 ? -1 : 0;
-
- /* Now record the full name of each file, including nonexistent ones. */
-
- if (name0 == 0)
- name0 = name1;
- if (name1 == 0)
- name1 = name0;
-
- inf[0].name = dir0 == 0 ? name0 : concat (dir0, ".", name0);
- inf[1].name = dir1 == 0 ? name1 : concat (dir1, ".", name1);
-
- /* Stat the files. Record whether they are directories.
- Record in stat_result whether stat fails. */
-
- for (i = 0; i <= 1; i++)
- {
- inf[i].length = 0;
- inf[i].timestamp[0] = 0;
- inf[i].dir_p = 0;
-
- if (inf[i].desc != -1)
- {
- if (get_filestat(inf[i].name, &inf[i]))
- errorcount = 1;
- }
-
- if (inf[i].desc == 0)
- inf[i].name = Standard_Input;
- }
-
- if (name0 == 0)
- inf[0].dir_p = inf[1].dir_p;
- if (name1 == 0)
- inf[1].dir_p = inf[0].dir_p;
-
- if (errorcount)
- {
- /* If either file should exist but fails to be opened, return 2. */
- val = 2;
- }
- else if (inf[0].dir_p && inf[1].dir_p)
- {
- if (output_style == OUTPUT_IFDEF)
- fatal ("-D option not supported with directories");
-
- /* If both are directories, compare the files in them. */
-
- if (depth > 0 && !recursive)
- {
- /* But don't compare dir contents one level down
- unless -r was specified. */
- message ("Common subdirectories: %s and %s\n",
- inf[0].name, inf[1].name);
- val = 0;
- }
- else
- {
- val = diff_dirs (inf[0].name, inf[1].name,
- compare_files, depth, 0, 0);
- }
-
- }
- else if (depth == 0 && (inf[0].dir_p || inf[1].dir_p))
- {
-
- /* If only one is a directory, and it was specified in the command line,
- use the file in that dir whose basename matches the other file. */
-
- int dir_arg = (inf[0].dir_p ? 0 : 1);
- int fnm_arg = (inf[0].dir_p ? 1 : 0);
- char *p;
- char *filename;
-
- if (inf[fnm_arg].name == Standard_Input)
- fatal ("can't compare - to a directory");
-
- p = rindex (inf[fnm_arg].name, '.');
- if (p == 0)
- p = rindex (inf[fnm_arg].name, ':');
-
- filename = concat (inf[dir_arg].name, ".", (p ? p+1 : inf[fnm_arg].name));
-
- if (get_filestat(filename,&inf[dir_arg]))
- {
- val = 2;
- }
- else
- {
- /* JF: patch from the net to check and make sure we can really free
- this. If it's from argv[], freeing it is a *really* bad idea */
- if (0 != (dir_arg ? dir1 : dir0))
- free (inf[dir_arg].name);
- inf[dir_arg].name = filename;
-
- if (inf[dir_arg].dir_p)
- {
- error ("%s is a directory but %s is not",
- inf[dir_arg].name, inf[fnm_arg].name);
- val = 1;
- }
- else
- val = diff_2_files (inf, depth);
- }
-
- }
- else if (depth > 0 && (inf[0].dir_p || inf[1].dir_p))
- {
- /* Perhaps we have a subdirectory that exists only in one directory.
- If so, just print a message to that effect. */
-
- if (inf[0].desc == -1 || inf[1].desc == -1)
- {
- if (entire_new_file_flag && recursive)
- val = diff_dirs (inf[0].name, inf[1].name, compare_files, depth,
- inf[0].desc == -1, inf[1].desc == -1);
- else
- {
- char *dir = (inf[0].desc == -1) ? dir1 : dir0;
- message ("Only in %s: %s\n", dir, name0);
- val = 1;
- }
- }
- else
- {
- /* We have a subdirectory in one directory
- and a file in the other. */
-
- if (inf[0].dir_p)
- message ("%s is a directory but %s is not\n",
- inf[0].name, inf[1].name);
- else
- message ("%s is a directory but %s is not\n",
- inf[1].name, inf[0].name);
- /* This is a difference. */
- val = 1;
- }
- }
- else
- {
- /* Both exist and both are ordinary files. */
- val = diff_2_files (inf, depth);
- }
-
- /* Now the comparison has been done, if no error prevented it,
- and VAL is the value this function will return. */
-
- if (inf[0].desc > 0)
- close (inf[0].desc);
- if (inf[1].desc > 0)
- close (inf[1].desc);
-
- if (val == 0 && !inf[0].dir_p)
- {
- if (print_file_same_flag)
- message ("Files %s and %s are identical\n",
- inf[0].name, inf[1].name);
- }
- else
- fflush (stdout);
-
- if (dir0 != 0)
- free (inf[0].name);
- if (dir1 != 0)
- free (inf[1].name);
-
- return val;
- }
-
- static int get_filestat (char *filename, struct file_data *inf)
- {
- int res;
- _kernel_osfile_block blk;
- _kernel_swi_regs regs;
- char time_blk[5];
-
- /* Standard input - return a desc of 0 */
- if (strcmp(filename, "-") == 0)
- {
- inf->desc = 0;
- return 0;
- }
-
- /* Try to get the file catalogue information */
- res = _kernel_osfile(5, filename, &blk);
-
- if (res == _kernel_ERROR)
- {
- _kernel_oserror *err = _kernel_last_oserror();
- fprintf(stderr,"%s: Cannot read %s (Error %d - %s)\n",
- program, filename, err->errnum, err->errmess);
- return 1;
- }
- else if (res == 0)
- {
- fprintf(stderr,"%s: Cannot read %s\n", program, filename);
- return 1;
- }
-
- /* Is the file a directory? */
- inf->dir_p = (res == 2);
-
- /* For a directory, simply set the file descriptor to -2 and return */
- if (res == 2)
- {
- inf->desc = -2;
- return 0;
- }
-
- /* Now set up the inf structure for a normal file */
- inf->length = blk.start;
- inf->desc = open (filename);
-
- if (inf->desc == -1)
- return 1;
-
- /* Finally, the timestamp. We leave it until last, as it's hard */
- if ((blk.load & 0xFFF00000) != 0xFFF00000)
- {
- inf->timestamp[0] = 0;
- return 0;
- }
-
- time_blk[0] = blk.exec & 0xFF;
- time_blk[1] = (blk.exec >> 8) & 0xFF;
- time_blk[2] = (blk.exec >> 16) & 0xFF;
- time_blk[3] = (blk.exec >> 24) & 0xFF;
- time_blk[4] = blk.load & 0xFF;
-
- regs.r[0] = (int)time_blk;
- regs.r[1] = (int)inf->timestamp;
- regs.r[2] = TIMELEN;
- regs.r[3] = (int)TIMEFORM;
-
- /* For errors, simply clear the timestamp */
- if (_kernel_swi(OS_ConvertDateAndTime,®s,®s))
- inf->timestamp[0] = 0;
-
- return 0;
- }
-
- /* Open and close a file descriptor. Register an atexit() handler to
- * tidy up files left open on exit.
- */
-
- static int open_fds[_SYS_OPEN];
- static int num_open = 0;
- static int registered = 0;
- static void close_fds (void);
-
- static int open (char *filename)
- {
- int fd;
-
- /* Register the close routine, if necessary */
- if (!registered)
- atexit (close_fds);
-
- /* Open the file for reading */
- fd = _kernel_osfind (0x4C, filename);
-
- /* Trap any OS errors, and return -1 */
- if (fd == _kernel_ERROR)
- {
- _kernel_oserror *err = _kernel_last_oserror();
- fprintf(stderr,"%s: Cannot open %s (Error %d - %s)\n",
- program, filename, err->errnum, err->errmess);
- return -1;
- }
- else if (fd == 0)
- {
- fprintf(stderr,"%s: Cannot open %s\n", program, filename);
- return -1;
- }
-
- /* Add the file to the list of open files */
- if (num_open < _SYS_OPEN)
- open_fds[num_open++] = fd;
-
- return fd;
- }
-
- static void close (int fd)
- {
- int i;
-
- /* Is this fd in the open list? */
- for (i = 0; i < num_open; ++i)
- {
- if (open_fds[i] == fd)
- break;
- }
-
- /* If so, remove it */
- if (i < num_open)
- {
- --num_open;
- open_fds[i] = open_fds[num_open];
- open_fds[num_open] = 0;
- }
-
- /* Close the file anyway */
- _kernel_osfind (0, (char *)fd);
- }
-
- static void close_fds (void)
- {
- int i;
-
- /* Close all the files registered as open */
- for (i = 0; i < num_open; ++i)
- _kernel_osfind (0, (char *)open_fds[i]);
- }
-